﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using HotelRegistrationSystem.Models;

namespace HotelRegistrationSystem.DAL
{
    public class ReservationsDAL
    {
        public static DbSet<ReservationType> GetReservationTypes()
        {
            HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities();
            DbSet<ReservationType> reservationTypes = db.ReservationTypes;
            return reservationTypes;
        }

        public static List<ReservationModel> GetReservations(int hotelId = 0, int statusId = 0)
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                var result =
                    (from r in db.Reservations.Include("ReservationsRooms").Include("Rooms")
                     let selectedHotelId = r.ReservationsRooms.FirstOrDefault().Room.HotelId
                     join rs in db.ReservationStatus on r.ReservationStatusId equals rs.ReservationStatusId
                     join rr in db.ReservationsRooms on r.ReservationId equals rr.ReservationId
                     join ro in db.Rooms on rr.RoomId equals ro.RoomId
                     where hotelId == 0 ? true : selectedHotelId.Equals(hotelId)
                     where statusId == 0 ? true : r.ReservationStatusId.Equals(statusId)
                     select new ReservationModel
                     {
                         ReservationId = r.ReservationId,
                         DateCreated = r.DateCreated,
                         StartDate = r.StartDate,
                         Period = r.Period,
                         ReservationStatusId = rs.ReservationStatusId,
                         HotelId = selectedHotelId,
                         RoomsCount = r.ReservationsRooms.Count,
                         UserName = r.User.FirstName + " " + r.User.LastName,
                         StatusName = rs.StatusName,
                         RoomName = ro.Name,
                         RoomId = ro.RoomId
                     }).OrderByDescending(s => s.ReservationId);

                return result.ToList();
            }
        }

        /// <summary>
        /// Check if the rooms to be reserved are available for the period.
        /// </summary>
        /// <param name="roomId">The id of the room to be reserved.</param>
        /// <param name="bedCount">The number of beds to be reserved.</param>
        /// <param name="start">The start date of the stay period.</param>
        /// <param name="end">The end date of stay period.</param>
        public static void CheckRoomAvailability(bool isHotelOwner, int roomId, int bedCount, DateTime start, DateTime end)
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                // If it's a dormitory check beds are free each day
                if (context.Rooms.FirstOrDefault(r => r.RoomId == roomId).RoomType.Type == "Обща")
                {
                    for (var day = start; day < end; day = day.AddDays(1))
                    {
                        var bedsCnt = 0;
                        foreach (var resRoom in context.ReservationsRooms.Where(r => r.RoomId == roomId && r.Count.HasValue))
                        {
                            if (resRoom.Reservation.ReservationStatus.ReservationStatusId == 2 &&
                                day >= resRoom.Reservation.StartDate &&
                                day < resRoom.Reservation.StartDate.AddDays(resRoom.Reservation.Period))
                            {
                                bedsCnt += resRoom.Count.Value;
                            }
                        }
                        if (context.Rooms.FirstOrDefault(r => r.RoomId == roomId).BedCount < bedsCnt + bedCount)
                        {
                            throw new ReservationException("No free beds in this period!");
                        }
                    }
                }
                else
                {
                    // Check if room is free
                    foreach (var resere in context.ReservationsRooms.Where(r => r.RoomId == roomId &&
                                                                                r.Reservation.ReservationStatus.ReservationStatusId == 2))
                    {
                        var reservationEndDate = resere.Reservation.StartDate.AddDays(resere.Reservation.Period);
                        if (resere.Reservation.ReservationStatus.ReservationStatusId == 2 &&
                            (resere.Reservation.StartDate < end && reservationEndDate > end) ||
                            (resere.Reservation.StartDate >= start && resere.Reservation.StartDate < end))
                        {
                            string message;
                            if (isHotelOwner) message =
                                string.Format("Room booked from {0:dd MMMM yyyy} to {1:dd MMMM yyyy}! You must cancel this reservation first.",
                                 resere.Reservation.StartDate,
                                 resere.Reservation.StartDate.AddDays(resere.Reservation.Period));
                            else
                                message = string.Format("Room booked from {0:dd MMMM yyyy} to {1:dd MMMM yyyy}!",
                                resere.Reservation.StartDate,
                                resere.Reservation.StartDate.AddDays(resere.Reservation.Period));

                            throw new ReservationException(message);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Adds a room reservation to database.
        /// </summary>
        /// <param name="roomId">The id of the room to be reserved.</param>
        /// <param name="username">The username of the user.</param>
        /// <param name="bedCount">The number of beds to be reserved.</param>
        /// <param name="start">The start date of the stay period.</param>
        /// <param name="end">The end date of stay period.</param>
        public static void ReserveRoom(bool isHotelOwner, int roomId, string username, int bedCount, DateTime start, DateTime end)
        {
            // Check if start date is before end date at least 1 day
            if (end.Subtract(start).Days < 1)
                throw new ReservationException("Invalid stay time!");

            using (var context = new HotelRegistrationSystemEntities())
            {
                /*// If it's a dormitory check beds are free each day
                if (context.Rooms.FirstOrDefault(r => r.RoomId == roomId).RoomType.Type == "Обща")
                {
                    for (var day = start; day < end; day.AddDays(1))
                    {
                        var bedsCnt = 0;
                        foreach (var resRoom in context.ReservationsRooms.Where(r => r.RoomId == roomId && r.Count.HasValue))
                        {
                            if (resRoom.Reservation.ReservationStatus.ReservationStatusId == 2 &&
                                day >= resRoom.Reservation.StartDate &&
                                day < resRoom.Reservation.StartDate.AddDays(resRoom.Reservation.Period))
                            {
                                bedsCnt += resRoom.Count.Value;
                            }
                        }
                        if (context.Rooms.FirstOrDefault(r => r.RoomId == roomId).BedCount < bedsCnt + bedCount)
                        {
                            throw new ReservationException("No free beds in this period!");
                        }
                    }
                }
                else
                {
                    // Check if room is free
                    foreach (var resere in context.ReservationsRooms.Where(r => r.RoomId == roomId &&
                                                                                r.Reservation.ReservationStatus.ReservationStatusId == 2))
                    {
                        var reservationEndDate = resere.Reservation.StartDate.AddDays(resere.Reservation.Period);
                        if (resere.Reservation.ReservationStatus.ReservationStatusId == 2 &&
                            (resere.Reservation.StartDate < end && reservationEndDate > end) ||
                            (resere.Reservation.StartDate > start && resere.Reservation.StartDate < end))
                        {
                            string message;
                            if (isHotelOwner) message =
                                string.Format("Room booked from {0:dd MMMM yyyy} to {1:dd MMMM yyyy}! You must cancel this reservation first.",
                                 resere.Reservation.StartDate,
                                 resere.Reservation.StartDate.AddDays(resere.Reservation.Period));
                            else
                                message = string.Format("Room booked from {0:dd MMMM yyyy} to {1:dd MMMM yyyy}!",
                                resere.Reservation.StartDate,
                                resere.Reservation.StartDate.AddDays(resere.Reservation.Period));

                            throw new ReservationException(message);
                        }
                    }
                }*/

                CheckRoomAvailability(isHotelOwner, roomId, bedCount, start, end);

                // Create reservation
                var userId = AccountDAL.GetUser(username).UserId;
                var user = context.Users.FirstOrDefault(u => u.UserId == userId);
                var reservationStatus = context.ReservationStatus.FirstOrDefault(r => r.ReservationStatusId == 1);
                var reservation = new Reservation
                {
                    DateCreated = DateTime.Now,
                    User = user,
                    StartDate = start,
                    Period = end.Subtract(start).Days,
                    ReservationStatus = reservationStatus
                };
                context.Reservations.Add(reservation);

                // Create reservationsrooms
                ReservationsRoom reservationsRooms = new ReservationsRoom
                {
                    Room = context.Rooms.FirstOrDefault(r => r.RoomId == roomId),
                    Count = bedCount
                };

                // Add to DB
                context.ReservationsRooms.Add(reservationsRooms);
                context.SaveChanges();
            }
        }

        public static void CancelReservation(int reservationId)
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                var reservation = context.Reservations.FirstOrDefault(r => r.ReservationId == reservationId);
                reservation.ReservationStatusId = 3;
                context.SaveChanges();

                //var reservationRoom = context.ReservationsRooms.FirstOrDefault(r => r.ReservationId == reservationId);
                //context.ReservationsRooms.Remove(reservationRoom);
                //context.SaveChanges();


                //if (context.Reservations.Count(r => r.ReservationId == reservationId) > 0)
                //{
                //    context.Reservations.Remove(context.Reservations.FirstOrDefault(r => r.ReservationId == reservationId));
                //    context.SaveChanges();
                //}
            }
        }

        public static int MarkReservationAsPrepaid(int reservationId)
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                var reservation = db.Reservations.FirstOrDefault(r => r.ReservationId == reservationId);
                if (reservation == null) return -1;
                reservation.ReservationStatusId = 4;
                db.SaveChanges();
                return 1;
            }
        }

        public static void AutomaticCancel()
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                foreach (var reservation in context.Reservations)
                {
                    var resTypeId = reservation
                    .ReservationsRooms
                    .FirstOrDefault(r => r.ReservationId == reservation.ReservationId)
                    .Room
                    .Hotel
                    .ReservationType
                    .ReservationTypeId;
                    // If reservation type is 'email' or 'telephone'
                    if (reservation.ReservationStatusId == 1 && (resTypeId == 3 || resTypeId == 4) &&
                        reservation.DateCreated.AddDays(3) < DateTime.Now)
                    {
                        // TODO: Talk with owner 3 days after reservation or 3 days before reservation StartDate
                        reservation.ReservationStatusId = 3;
                    }
                }
                context.SaveChanges();
            }
        }

        public static bool UpdateReservationStatusId(int reservationId, int statusId)
        {
            using (HotelRegistrationSystemEntities db = new HotelRegistrationSystemEntities())
            {
                var reservation = db.Reservations.FirstOrDefault(r => r.ReservationId.Equals(reservationId));
                if (reservation != null)
                {
                    reservation.ReservationStatusId = statusId;
                    db.SaveChanges();
                    db.Dispose();
                    return true;
                }
                else
                {
                    db.Dispose();
                    return true;
                }
            }
        }

        public static IList<Reservation> GetUserReservations(string username)
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                var userId = AccountDAL.GetUser(username).UserId;
                return context.Reservations.Include("ReservationStatus").Where(r => r.UserId == userId).ToList();
            }
        }

        public static string GetHotelNameFromReservation(int reservationId)
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                return context
                    .Reservations
                    .FirstOrDefault(r => r.ReservationId == reservationId)
                    .ReservationsRooms
                    .FirstOrDefault(r => r.ReservationId == reservationId)
                    .Room
                    .Hotel
                    .Name;
            }
        }

        public static int GetBedsOfReservation(int reservationId)
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                var reservedBeds = context
                    .Reservations
                    .FirstOrDefault(r => r.ReservationId == reservationId)
                    .ReservationsRooms
                    .FirstOrDefault(r => r.ReservationId == reservationId)
                    .Count;
                return reservedBeds.HasValue ?
                    reservedBeds.Value :
                    context
                    .Reservations
                    .FirstOrDefault(r => r.ReservationId == reservationId)
                    .ReservationsRooms
                    .FirstOrDefault(r => r.ReservationId == reservationId)
                    .Room
                    .BedCount;
            }
        }

        public static decimal GetPriceOfReservation(int reservationId)
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                var reservation = context
                    .Reservations
                    .FirstOrDefault(r => r.ReservationId == reservationId);
                return ((decimal)reservation
                    .ReservationsRooms
                    .FirstOrDefault(r => r.ReservationId == reservation.ReservationId)
                    .Room
                    .Price
                    * reservation.Period);
            }
        }

        public static int GetReservationTypeIdFromReservation(int reservationId)
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                var reservation = context
                    .Reservations
                    .FirstOrDefault(r => r.ReservationId == reservationId);
                return reservation
                    .ReservationsRooms
                    .FirstOrDefault(r => r.ReservationId == reservation.ReservationId)
                    .Room
                    .Hotel
                    .ReservationType
                    .ReservationTypeId;
            }
        }

		public static string GetReservationsStatusName(int reservationStatusId)
		{
			using (var context = new HotelRegistrationSystemEntities())
			{
				return context.ReservationStatus.FirstOrDefault(r => r.ReservationStatusId == reservationStatusId).StatusName;
			}
		}

        public static RoomReservesManageModel GetRoomReservations(int roomId)
        {
            using (var context = new HotelRegistrationSystemEntities())
            {
                var room = context.Rooms.FirstOrDefault(r => r.RoomId == roomId);
                var roomResModel = new RoomReservesManageModel(room.Hotel.Name, room.Name, roomId);
                foreach (var res in context.ReservationsRooms.Where(r => r.RoomId == roomId))
                {
                    roomResModel.RoomReservations.Add(new RoomReserveManageModel(
                        res.Reservation.User.UserName, 
                        res.Count.HasValue ? res.Count.Value : res.Room.BedCount,
                        res.Reservation.StartDate, 
                        res.Reservation.StartDate.AddDays(res.Reservation.Period), 
                        res.ReservationId,
                        res.Reservation.ReservationStatusId));
                }

                return roomResModel;
            }
        }
    }

    public class ReservationException : Exception
    {
        public new string Message { get; set; }

        public ReservationException(string message)
        {
            Message = message;
        }
    }
}